#include "StdAfx.h"
#include "AudioOscilloscope.h"
#include <stdlib.h>
#include <stdio.h>

const int KDrawSleepMillionSeconds=80;//million seconds
const int kHalfKOscilloscopeHeight=KOscilloscopeHeight/2;
const int KDrawOscStyle=2;

AudioOscilloscope::AudioOscilloscope()
{
	m_buffer=new MutableBuffer;
	m_isInited=false;
	m_wnd=NULL;

	reset();
}

AudioOscilloscope::~AudioOscilloscope(void)
{
	if (m_threadId>0)
	{
		if(m_draw)
		{
			m_draw=false;
		}
		Sleep(KDrawSleepMillionSeconds*2);
	}

	delete m_buffer;
}

void AudioOscilloscope::reset()
{
	m_bytesPerSample=0;
	m_channelCount=0;
	m_sampleCount=0;
	m_totalSeconds=0;

	m_buffer->clear();
	m_isBigBuffer=false;

	m_draw=false;
	m_lastClock=0;
	m_threadId=0;
}

void AudioOscilloscope::setAudioBuffer(MutableBuffer* buffer,const WAVEFORMATEX& waveFormatex)
{
	setAudioBuffer(buffer->buffer(),buffer->bufferSize(),waveFormatex);
}

void AudioOscilloscope::setAudioBuffer(const void* newBuffer,int newBufferSize,const WAVEFORMATEX& waveFormatex)
{
	reset();

	m_buffer->setBuffer(newBuffer,newBufferSize);
	m_isBigBuffer=true;
	setWaveFormatex(waveFormatex);
}

void AudioOscilloscope::addAudioBuffer(const void* newBuffer,int newBufferSize,const WAVEFORMATEX& waveFormatex)
{
	Lock lock(&m_lockSection);
	m_buffer->addBuffer(newBuffer,newBufferSize);
	setWaveFormatex(waveFormatex);
}

void AudioOscilloscope::setWaveFormatex(const WAVEFORMATEX& waveFormatex)
{
	if(m_channelCount==0)
	{
		m_tempWaveFormatex=waveFormatex;

		m_channelCount=m_tempWaveFormatex.nChannels;
		m_bytesPerSample=m_tempWaveFormatex.wBitsPerSample/8*m_channelCount;

		m_sampleCount=m_buffer->bufferSize()/m_bytesPerSample/m_channelCount;
		m_totalSeconds=m_sampleCount*1.0 / m_tempWaveFormatex.nSamplesPerSec * m_channelCount;
	}
}

bool AudioOscilloscope::getAudioBuffer(MutableBuffer* buffer)
{
	bool ret=false;

	if(m_isBigBuffer)
	{
		if(m_buffer->cursoredBufferSize()>0)
		{
			clock_t clockk;
			clockk=clock();

			int diffMillionSeconds=(clockk-m_lastClock)*1.0/CLOCKS_PER_SEC*1000;
			if(diffMillionSeconds<=0)
			{
				diffMillionSeconds=KDrawSleepMillionSeconds;
			}
				
			float percentage=diffMillionSeconds/(m_totalSeconds*1000.0);
			ret=m_buffer->getCursoredBuffer(buffer,percentage);

			m_lastClock=clockk;
		}
	}
	else
	{
		Lock lock(&m_lockSection);
		if(m_buffer->bufferSize()>0)
		{
			buffer->setBuffer(m_buffer);
			m_buffer->clear();

			ret = true;
		}
	}

	return ret;
}

void AudioOscilloscope::draw(CWnd* wnd,const CPoint& pos)
{
	m_wnd=wnd;
	m_bitBltPos=pos;

	m_draw=true;
	m_threadId=0;

	if(m_isBigBuffer)
	{
		m_lastClock=clock();
	}

	HANDLE hThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)CallRun,this,0,&m_threadId);
	SetThreadPriority(hThread,-4);
	ResumeThread(hThread); 
	CloseHandle(hThread);  
}

void AudioOscilloscope::endDraw()
{
	m_draw=false;

	if(m_threadId>0)
	{
		Sleep(KDrawSleepMillionSeconds*2);
	}

	reset();
}

void AudioOscilloscope::CallRun(LPVOID p)
{
	AudioOscilloscope* pAudioEngine=(AudioOscilloscope*)p;
	
	while(true)
	{
		MutableBuffer buffer;
		bool hasBuffer=pAudioEngine->getAudioBuffer(&buffer);
		pAudioEngine->draw(buffer.buffer(),buffer.bufferSize(),!hasBuffer);

		if(!hasBuffer && pAudioEngine->m_isBigBuffer)
		{
			pAudioEngine->m_draw=false;
		}

		if(!pAudioEngine->m_draw)
		{
			pAudioEngine->draw(NULL,0,true);
			pAudioEngine->reset();
			break;
		}

		Sleep(KDrawSleepMillionSeconds);

		if(!pAudioEngine->m_draw)
		{
			pAudioEngine->draw(NULL,0,true);
			pAudioEngine->reset();
			break;
		}
	}
}

void AudioOscilloscope::draw(unsigned char* pBuffer,unsigned int bufferSize,bool onlyDrawBg)
{
	if(!onlyDrawBg && (!pBuffer || bufferSize<=0))
	{
		onlyDrawBg=true;
	}

	//prepare
	CClientDC clientDC(m_wnd);

	if(!m_isInited)
	{
		m_isInited=true;
		m_compatibleDC.CreateCompatibleDC(&clientDC);
		m_bitmap.CreateCompatibleBitmap(&clientDC,KOscilloscopeWidth,KOscilloscopeHeight);
		m_compatibleDC.SelectObject(&m_bitmap);
	}

	//bg
	CBrush brush(RGB(0,0,0));
	CBrush* oldBrush=m_compatibleDC.SelectObject(&brush);
	m_compatibleDC.Rectangle(CRect(0,0,KOscilloscopeWidth,KOscilloscopeHeight));
	m_compatibleDC.SelectObject(oldBrush);

	//base line
	CPen pen(PS_SOLID,0,RGB(255,0,0));
	CPen* oldPen=m_compatibleDC.SelectObject(&pen);
	m_compatibleDC.MoveTo(0,KOscilloscopeHeight/2);
	m_compatibleDC.LineTo(KOscilloscopeWidth,KOscilloscopeHeight/2);
	m_compatibleDC.SelectObject(oldPen);

	if(m_draw && !onlyDrawBg)
	{
		//points
		CPen pen(PS_SOLID,0,RGB(255,255,255));
		CPen* oldPen=m_compatibleDC.SelectObject(&pen);

		
		int perChannelBytes=m_bytesPerSample/m_channelCount;
		float halfRange=m_bytesPerSample/m_channelCount==2?32768.0:128.0;
		int lastX=0;
		int lastLeftY=kHalfKOscilloscopeHeight;
		int lastRightY=kHalfKOscilloscopeHeight;
		int sampleCount=bufferSize/m_bytesPerSample/m_channelCount;

		for (int i=0;i<sampleCount;i++)
		{
			int x=i*1.0/sampleCount*KOscilloscopeWidth;

			//left
			int leftValue=getSampleValue(pBuffer,perChannelBytes);
			int y=kHalfKOscilloscopeHeight*(1-abs(leftValue)/halfRange);
			drawPoint(x,y,lastX,lastLeftY);
			lastLeftY=y;

			//right
			if(m_channelCount==2)
			{
				int rightValue=getSampleValue(pBuffer+perChannelBytes,perChannelBytes);
				int y=kHalfKOscilloscopeHeight*(1+abs(rightValue)/halfRange);
				drawPoint(x,y,lastX,lastRightY);
				lastRightY=y;
			}

			lastX=x;

			pBuffer+=m_bytesPerSample;
		}

		m_compatibleDC.SelectObject(oldPen);
	}

	clientDC.BitBlt(m_bitBltPos.x,m_bitBltPos.y,KOscilloscopeWidth,KOscilloscopeHeight,&m_compatibleDC,0,0,SRCCOPY);
}

void AudioOscilloscope::drawPoint(int x,int y,int lastX,int lastY)
{
	switch(KDrawOscStyle)
	{
		case 0:
			{
				m_compatibleDC.SetPixel(CPoint(x,y), RGB(255,255,255));
			}
			break;
		case 1:
			{
				m_compatibleDC.MoveTo(lastX,lastY);
				m_compatibleDC.LineTo(x,y);
			}
			break;
		case 2:
			{
				m_compatibleDC.MoveTo(x,kHalfKOscilloscopeHeight);
				m_compatibleDC.LineTo(x,y);
			}
			break;
	}
}

int AudioOscilloscope::getSampleValue(unsigned char* pBuffer,int perChannelBytes)
{
	short lowPosValue=*pBuffer;

	if(perChannelBytes==1)
	{
		return lowPosValue;
	}
	else
	{
		int valuee=0;

		short highPosValue=(*(pBuffer+1))<<8;
		if(highPosValue>=0)
		{
			valuee+=highPosValue;
		}
		else
		{
			valuee=highPosValue-lowPosValue;
		}

		return valuee;
	}
}